package com.yhy.common.service;

import com.yhy.common.utils.RedisUtil;

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/*
 *
 *  * *
 *  *  * <br>
 *  *  * <b>功能：</b><br>
 *  *  * <b>作者：</b>yanghuiyaun<br>
 *  *  * <b>日期：</b> 19-12-5 下午4:56 <br>
 *  *  * <b>版权所有：<b>版权所有(C) 2019<br>
 *  *
 *
 */
public class RedisLock {

    // 存储到redis中的锁标志
    private static final String LOCKED = "LOCKED";

    // 默认请求锁的超时时间(2S)
    private static final long DEFAULT_TIME_OUT = 1000 * 2;

    // 锁的有效时间(s)
    public static final int EXPIRE = 60;

    // 锁标志对应的key;
    private String key;

    private Long customTimeOut;

    //标识位
    private volatile boolean isLocked = false;

    public RedisLock(String key) {
        this.key = key;
        this.customTimeOut = DEFAULT_TIME_OUT;
    }

    public RedisLock(String key, Long customTimeOut) {
        this.key = key;
        this.customTimeOut = customTimeOut;
    }

    public Boolean lock() {
        //系统当前时间，纳秒 1纳秒=0.000001 毫秒
        long nowTime = System.nanoTime();
        long timeout = this.customTimeOut * 1000000; //请求锁超时时间，毫秒
        final Random r = new Random();
        try {
            //不断循环向Master节点请求锁，当请求时间(System.nanoTime() - nano)超过设定的超时时间则放弃请求锁
            //这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间
            //如果一个master节点不可用了，应该尽快尝试下一个master节点
            while ((System.nanoTime() - nowTime) < timeout) {
                //将锁作为key存储到redis缓存中，存储成功则获得锁
                if (RedisUtil.setNX(key, LOCKED)) {
                    //设置锁的有效期，也是锁的自动释放时间，也是一个客户端在其他客户端能抢占锁之前可以执行任务的时间
                    //可以防止因异常情况无法释放锁而造成死锁情况的发生
                    RedisUtil.setExpireTime(key, EXPIRE);
                    isLocked = true;
                    //上锁成功结束请求
                    break;
                }
                //获取锁失败时，应该在随机延时后进行重试，避免不同客户端同时重试导致谁都无法拿到锁的情况出现
                //睡眠3毫秒后继续请求锁
                Thread.sleep(3, r.nextInt(500));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return isLocked;
    }

    public void unlock() {
        if(isLocked) {
            RedisUtil.remove(key);
        }
    }

}
